package com.sailing.xjpb.web.base.converter;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider;
import com.fasterxml.jackson.databind.ser.SerializerFactory;
import com.fasterxml.jackson.databind.ser.std.NullSerializer;
import com.sailing.xjpb.common.ClassUtils;
import com.sailing.xjpb.web.base.converter.annotation.NullObjectSerializer;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

import java.io.IOException;
import java.util.List;
import java.util.Optional;

/**
 * 1、将字符串null值转换为""|将array和list=null转换为“[]”。
 * 2、将标注有@SailingNullObjectSerializer注解的字段为null时，先调用new instance初始化后，再序列化json。
 *
 * @author YaoWei
 * @see com.sailing.xjpb.web.base.configuration.SailingWebMvcConfigurerAdapter
 * @see NullObjectSerializer
 */
public class JacksonHttpMessageConverter extends MappingJackson2HttpMessageConverter {

    public JacksonHttpMessageConverter(ObjectMapper objectMapper) {
        super(objectMapper);
        // 普通字段的空值序列化处理器
        SerializerProvider serializerProvider = objectMapper.getSerializerProvider();
        serializerProvider.setNullValueSerializer(SailingNullStringSerializer.instance);
        // 设置bean对象属性字段空值序列化处理器
        SerializerFactory serializerFactory = getObjectMapper().getSerializerFactory();
        objectMapper.setSerializerProvider((DefaultSerializerProvider) serializerProvider);
        objectMapper.setSerializerFactory(serializerFactory.withSerializerModifier(new SailingBeanSerializerModifier()));
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    /**
     * 为JavaBean中的属性字段设置空值序列化处理器
     *
     * @author YaoWei
     * createDate: 2020年12月5日 下午6:01:48
     */
    public static class SailingBeanSerializerModifier extends BeanSerializerModifier {

        @Override
        public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc,
                                                         List<BeanPropertyWriter> beanProperties) {
            for (BeanPropertyWriter writer : beanProperties) {
                // 设置属性的空值序列化工具
                Class<?> clazz = writer.getType().getRawClass();
                if (ClassUtils.isArrayType(clazz)) {
                    // 数组
                    writer.assignNullSerializer(SailingNullArraySerializer.instance);
                } else if (ClassUtils.isNumberType(clazz)) {
                    // 数字
                    writer.assignNullSerializer(SailingNullNumberSerializer.instance);
                } else if (ClassUtils.isBooleanType(clazz)) {
                    // boolean类型
                    writer.assignNullSerializer(SailingNullBooleanSerializer.instance);
                } else if (ClassUtils.isMapType(clazz)) {
                    // map类型
                    writer.assignNullSerializer(SailingNullArrayMapSerializer.instance);
                } else if (ClassUtils.isStringType(clazz)) {
                    // String字符串
                    writer.assignNullSerializer(SailingNullStringSerializer.instance);
                } else if (useSailingNullObjectSerializer(writer, beanDesc)) {
                    // 使用定义的空值序列化工具
                    writer.assignNullSerializer(new SailingNullObjectSerializer(clazz));
                } else {
                    // 使用自带的空值序列化工具
                    writer.assignNullSerializer(NullSerializer.instance);
                }
            }
            return beanProperties;
        }

        /**
         * 对象为null时，是否使用SailingNullObjectSerializer序列化
         * </p>
         * 对标注有@SailingNullObjectSerializer注解的字段使用SailingNullObjectSerializer序列化
         *
         * @param writer   字段序列化对象
         * @param beanDesc bean描述信息
         * @return true：使用SailingNullObjectSerializer序列化
         */
        private boolean useSailingNullObjectSerializer(BeanPropertyWriter writer, BeanDescription beanDesc) {
            String fieldName = writer.getName();
            List<BeanPropertyDefinition> properties = beanDesc.findProperties();
            Optional<BeanPropertyDefinition> propertyDefinitionOpt =
                    properties.stream().filter(prop -> fieldName.equals(prop.getName())).findFirst();
            if (propertyDefinitionOpt.isPresent()) {
                BeanPropertyDefinition propertyDefinition = propertyDefinitionOpt.get();
                AnnotatedField field = propertyDefinition.getField();
                return field != null && field.getAnnotation(NullObjectSerializer.class) != null;
            }
            return false;
        }
    }

    /**
     * 处理数组类型的null值
     */
    public static class SailingNullArraySerializer extends JsonSerializer<Object> {

        public static final SailingNullArraySerializer instance = new SailingNullArraySerializer();

        private SailingNullArraySerializer() {
            super();
        }

        @Override
        public void serialize(Object value, JsonGenerator jsonGenerator, SerializerProvider provider) throws IOException {
            jsonGenerator.writeStartArray();
            jsonGenerator.writeEndArray();
        }
    }

    /**
     * 处理Map类型的null值
     */
    public static class SailingNullArrayMapSerializer extends JsonSerializer<Object> {

        public static final SailingNullArrayMapSerializer instance = new SailingNullArrayMapSerializer();

        private SailingNullArrayMapSerializer() {
            super();
        }

        @Override
        public void serialize(Object value, JsonGenerator jsonGenerator, SerializerProvider provider) throws IOException {
            jsonGenerator.writeStartObject();
            jsonGenerator.writeEndObject();
        }
    }

    /**
     * 处理字符串类型的null值
     */
    public static final class SailingNullStringSerializer extends JsonSerializer<Object> {

        public static final SailingNullStringSerializer instance = new SailingNullStringSerializer();

        private SailingNullStringSerializer() {
            super();
        }

        @Override
        public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
                throws IOException {
            jsonGenerator.writeString(StringUtils.EMPTY);
        }
    }

    /**
     * 处理数字类型的null值
     */
    public static final class SailingNullNumberSerializer extends JsonSerializer<Object> {

        public static final SailingNullNumberSerializer instance = new SailingNullNumberSerializer();

        private SailingNullNumberSerializer() {
            super();
        }

        @Override
        public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
                throws IOException {
            jsonGenerator.writeNumber(0);
        }
    }

    /**
     * 处理布尔类型的null值
     */
    public static final class SailingNullBooleanSerializer extends JsonSerializer<Object> {

        public static final SailingNullBooleanSerializer instance = new SailingNullBooleanSerializer();

        private SailingNullBooleanSerializer() {
            super();
        }

        @Override
        public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
                throws IOException {
            jsonGenerator.writeBoolean(Boolean.FALSE);
        }
    }

    /**
     * 处理对象类型的null值-对com.sailing下的包类进行处理
     * 为啥不处理其他包下的类？因为其他类型太多了，兼容不过来，如果需要，自行在业务代码中处理。
     */
    @Slf4j
    public static class SailingNullObjectSerializer extends JsonSerializer<Object> {

        private final Class<?> clazz;

        private Object defaultValueWhenNull;

        private SailingNullObjectSerializer(Class<?> clazz) {
            super();
            this.clazz = clazz;
        }

        @Override
        public void serialize(Object value, JsonGenerator jsonGenerator, SerializerProvider provider) throws IOException {
            try {
                if (defaultValueWhenNull == null) {
                    defaultValueWhenNull = clazz.newInstance();
                }
                jsonGenerator.writeObject(defaultValueWhenNull);
            } catch (Exception e) {
                log.warn("类：{}-调用初始化方法newInstance()失败，将使用默认的null值序列化工具！", clazz.getName(), e);
                jsonGenerator.writeNull();
            }
        }

    }
}